﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BesAsm.Swsp.PacSizingTool
{
  /// <summary>
  /// Class representing a stormwater mananagement facility
  /// </summary>
  public class Facility
  {
    internal bool _validConfiguration;

    protected double _bottomAreaSqFt;
    protected double _bottomWidthFt;

    protected double _storageDepth1In;    

    protected double _surfaceAreaAtStorageDepth1SqFt; //For User-Specified facility shapes
    protected double _bottomPerimeterLengthFt; //For Amoeba facility shapes
    protected double _surfaceAreaAtStorageDepth2SqFt; //For E-type User-Specified facility shapes

    protected double _growingMediumDepthIn;

    protected double _sideSlopeRatio;
    protected bool _specifySideSlope;

    protected double _freeboardIn;
    protected bool _specifyFreeboard;

    protected bool _hasSecondaryOverflow;
    protected double _storageDepth2In;    

    protected bool _hasRockStorage;
    protected double _rockStorageBottomAreaSqFt;
    protected bool _hasCustomRockStorageBottomArea;
    protected double _rockStorageDepthIn;
    protected double _rockVoidRatio;

    protected bool _hasRockInfluencedSurfaceStorage;
    protected double _storageDepth3In;

    internal FacilityShape _facilityShape;
    protected bool _allowShapeSelection;

    protected FacilityType _type;
    protected FacilityConfiguration _configuration;
    protected Catchment _catchment;

    protected bool _isLined;

    protected const double DefaultGrowingMediumPorespace = 0.3;

    const bool SANITIZEINPUT = false;    

    /// <summary>
    /// Constructs a new Facility object of the specified Type and Configuration, located within
    /// the specified Catchment
    /// </summary>
    /// <param name="type">The type of facility</param>
    /// <param name="configuration">The plumbed facility configuration</param>
    /// <param name="catchment">The Catchment containing the facility</param>
    public Facility(FacilityType type, FacilityConfiguration configuration, Catchment catchment)
    {
      _type = type;
      _configuration = configuration;
      _catchment = catchment;

      ConfigureFacility();
    }

    /// <summary>
    /// Returns enumerator indicating the facilities' plumbed connections to existing
    /// storm sewer and/or groundwater
    /// </summary>
    public FacilityConfiguration Configuration
    {
      get { return _configuration; }
    }

    /// <summary>
    /// Returns enumerator indicating the type of facility
    /// </summary>
    public FacilityType Type
    {
      get { return _type; }
    }

    /// <summary>
    /// The bottom area of the facility in square feet
    /// </summary>
    public virtual double BottomAreaSqFt
    {
      get { return _bottomAreaSqFt; }
      set { _bottomAreaSqFt = value; }

    }

    /// <summary>
    /// The surface area at storage depth 1 in square feet. Used only for User-Defined facility
    /// shape in Basin facility
    /// </summary>
    public double SurfaceAreaAtStorageDepth1SqFt
    {
      get {
          if(_type == FacilityType.PlanterFlat)
          {
              return _bottomAreaSqFt;
          }
          else
            return _surfaceAreaAtStorageDepth1SqFt; 
      }
      set { _surfaceAreaAtStorageDepth1SqFt = value; }
    }

    /// <summary>
    /// The surface area at storage depth 2 in square feet. Used only for E-type User-Defined facility
    /// shape in Basin facility
    /// </summary>
    public double SurfaceAreaAtStorageDepth2SqFt
    {
      get { return _surfaceAreaAtStorageDepth2SqFt; }
      set { _surfaceAreaAtStorageDepth2SqFt = value; }
    }

    /// <summary>
    /// The bottom perimeter length in feet. Used only for Amoeba shaped facility
    /// types in Basin facility
    /// </summary>
    public double BottomPerimeterLengthFt
    {
      get { return _bottomPerimeterLengthFt; }
      set { _bottomPerimeterLengthFt = value; }
    }

    /// <summary>
    /// The width of the facility in square feet
    /// </summary>
    public double BottomWidthFt
    {
      get { return _bottomWidthFt; }
      set { _bottomWidthFt = value; }
    }

    /// <summary>
    /// The storage depth before a facility reaches it's first overflow point
    /// </summary>
    public double StorageDepth1In
    {
      get { return _storageDepth1In; }
      set
      {
        _storageDepth1In = value;        
      }
    }
    /// <summary>
    /// The volume of storage capacity in cubic feet on the surface of the facility 
    /// before reaching the primary overflow
    /// </summary>
    public virtual double SurfaceCapacityAtDepth1CuFt
    {
      get { return CalculateVolume(_type, _facilityShape, _storageDepth1In, _surfaceAreaAtStorageDepth1SqFt); }
    }

    /// <summary>
    /// The storage depth (in inches) before a facility reaches it's second overflow point, if applicable;
    /// See field <para name="HasSecondaryOverflow">Facility.HasSecondaryOverflow</para> to determine if second overflow point is applicable.
    /// </summary>
    public double StorageDepth2In
    {
      get { return _storageDepth2In; }
      set
      {
        if (_hasSecondaryOverflow)
        {
          _storageDepth2In = value;       
        }
      }
    }

    /// <summary>
    /// The volume of storage capacity in cubic feet on the surface of the facility 
    /// before reaching the secondary overflow, if applicable; See field 
    /// <para name="HasSecondaryOverflow">Facility.HasSecondaryOverflow</para> 
    /// to determine if this field is applicable.
    /// </summary>
    public virtual double SurfaceCapacityAtDepth2CuFt
    {
      get { return CalculateVolume(_type, _facilityShape, _storageDepth2In, _surfaceAreaAtStorageDepth2SqFt); }
    }

    /// <summary>
    /// The storage depth (in inches) before a facilities' rock gallery overflows offsite, if applicable;
    /// See field <para name="HasRockInfluencedSurfaceStorage">Facility.HasRockInfluencedSurfaceStorage</para> 
    /// will be false if this field is applicable.
    /// </summary>
    public double StorageDepth3In
    {
      get { return _storageDepth3In; }
      set
      {
        if (!_hasRockInfluencedSurfaceStorage)
        {
          _storageDepth3In = value;
        }
      }
    }

    /// <summary>
    /// The facility Freeboard (in inches). Not sure how this parameter is used...
    /// See field <para name="SpecifyFreeboard">Facility.SpecifyFreeboard</para> to
    /// determine if this field is applicable.
    /// </summary>
    public double FreeboardIn
    {
      get { return _freeboardIn; }
      set { _freeboardIn = value; }
    }

    /// <summary>
    /// Indicates whether this facility configuration has a user-specifiable Freeboard
    /// </summary>
    public bool SpecifyFreeboard
    {
      get { return _specifyFreeboard; }
    }

    /// <summary>
    /// The depth of the growing medium, in inches
    /// </summary>
    public double GrowingMediumDepthIn
    {
      get { return _growingMediumDepthIn; }
      set { _growingMediumDepthIn = value; }
    }

    /// <summary>
    /// The slope of the facility side-slope, specified in the ratio of X:1
    /// See field <paramref name="SpecifySideSlope">Facility.SpecifySideSlope</paramref> to
    /// determine if this field is applicable.
    /// </summary>
    public double SideSlopeRatio
    {
      get { return _sideSlopeRatio; }
      set
      {
        _sideSlopeRatio = value;
      }
    }

    /// <summary>
    /// Indicates whether this facility configuration has a user-specifiable SideSlope
    /// </summary>
    public bool SpecifySideSlope
    {
      get { return _specifySideSlope; }
    }

    /// <summary>
    /// The storage area of the rock gallery in square feet
    /// See field <para name="HasRockStorage">Facility.HasRockStorage</para> to
    /// determine if the facility has a rock gallery, and field
    /// <para name="HasCustomRockStorageBottomArea">Facility.HasCustomRockStorageBottomArea</para>
    /// to determine whether the rock gallery area is customizable
    /// </summary>
    public virtual double RockStorageBottomAreaSqFt
    {
      get { return _rockStorageBottomAreaSqFt; }
      set
      {
        if (value <= 0 && SANITIZEINPUT)
          throw new ArgumentException("Rock Storage Bottom Area must be greater than zero.");
        _rockStorageBottomAreaSqFt = value;
      }
    }

    /// <summary>
    /// The depth of the rock storage gallery in inches
    /// See field <paramref name="HasRockStorage">Facility.HasRockStorage</paramref> to
    /// determine if this field is applicable.
    /// Values greater than 48 inches should trigger a warning.
    /// </summary>
    public double RockStorageDepthIn
    {
      get { return _rockStorageDepthIn; }
      set
      {
        if (SANITIZEINPUT && value <= 0)
          throw new ArgumentException("Rock Storage Depth be greater than zero.");
        _rockStorageDepthIn = value;
      }
    }

    /// <summary>
    /// The void ratio of the rock gallery
    /// See field <paramref name="HasRockStorage">Facility.HasRockStorage</paramref> to
    /// determine if this field is applicable.
    /// Values greater than 0.4 should trigger a warning.
    /// </summary>
    public double RockVoidRatio
    {
      get { return _rockVoidRatio; }
      set
      {
        if (SANITIZEINPUT && value <= 0)
          throw new ArgumentException("Rock Void Ratio must be greater than zero.");
        _rockVoidRatio = value;
      }
    }

    /// <summary>
    /// Indicates whether the facility has a rock gallery
    /// </summary>
    public bool HasRockStorage
    {
      get { return _hasRockStorage; }
      set { _hasRockStorage = value; }
    }

    /// <summary>
    /// Indicates whether the facility has a customizable rock storage bottom area;
    /// if not, the facility uses the facility bottom area as the rock storage bottom area
    /// </summary>
    public bool HasCustomRockStorageBottomArea
    {
      get { return _hasCustomRockStorageBottomArea; }
    }

    /// <summary>
    /// Indicates whether stormwater stored in the rock gallery can influence the volume
    /// of water stored on the surface of the facility. For facilities where the rock
    /// gallery is plumbed directly to a storm sewer this field will be false.
    /// </summary>
    public bool HasRockInfluencedSurfaceStorage
    {
      get { return _hasRockInfluencedSurfaceStorage; }
    }

    /// <summary>
    /// Enumerator describing the shape of the facility
    /// </summary>
    public FacilityShape Shape
    {
      get { return _facilityShape; }
      set { _facilityShape = value; }
    }

    /// <summary>
    /// Indicates whether the shape of the facility is customizable
    /// </summary>
    public bool AllowShapeSelection
    {
      get { return _allowShapeSelection; }
    }

    /// <summary>
    /// Indicates whether the facility has a secondary overflow pipe
    /// </summary>
    public bool HasSecondaryOverflow
    {
      get { return _hasSecondaryOverflow; }
    }

    /// <summary>
    /// The total area of the facility at the design storage depth in square feet.
    /// </summary>
    public virtual double TotalFacilityAreaSqFt
    {
      get
      {
        switch (this.Type)
        {
          case FacilityType.PlanterFlat:
            return _bottomAreaSqFt;
          case FacilityType.Basin:
            switch (this.Shape)
            {
              case FacilityShape.Rectangle:
                return
                  (_bottomAreaSqFt / _bottomWidthFt) +
                  (
                    2 * _sideSlopeRatio *
                    ((_storageDepth1In + _freeboardIn) / 12) *
                    (_bottomWidthFt + (2 * _sideSlopeRatio * (_storageDepth1In + _freeboardIn) / 12))
                  );
              case FacilityShape.Amoeba:
                return (_bottomAreaSqFt + _bottomPerimeterLengthFt * (_storageDepth1In + _freeboardIn) / 12 * _sideSlopeRatio);
              case FacilityShape.UserDefined:
                return _surfaceAreaAtStorageDepth1SqFt;
              default:
                return -1;
            }
          case FacilityType.PlanterSloped:
            return -1; //Overridden by SlopedFacility class
          case FacilityType.Swale:
            return -1; //Overridden by SlopedFacility class
          default: return -1;
        }
      }
    }

    /// <summary>
    /// The ratio of the facility footprint to the catchment impervious area expressed as a fraction.
    /// </summary>
    public double FacilitySizingRatio
    {
      get
      {
        return TotalFacilityAreaSqFt / _catchment.ImperviousAreaSquareFeet;
      }
    }

    /// <summary>
    /// Indicates whether the facility includes an impermeable membrane preventing
    /// infiltration to native soil
    /// </summary>
    public bool IsLined { get { return _isLined; } }

    /// <summary>
    /// The area of stormwater in contact with soil when the facility is
    /// 75% full
    /// </summary>
    public virtual double InfiltAreaAt75PercentDepth1SqFt
    {
      get
      {
        double ia75;

        ia75 =
          (BottomAreaSqFt +
          3 * (2.0 * SurfaceCapacityAtDepth1CuFt / (StorageDepth1In / 12) - BottomAreaSqFt)) / 4.0;

        return ia75;
      }
    }

    /// <summary>
    /// The infiltration rate through imported soil in cfs
    /// </summary>
    /// <returns></returns>
    public double ImportedMediumInfiltrationCapacityCfs()
    {
      switch (_type)
      {
        case FacilityType.Basin:
          return _catchment.ImportedMediumInfiltrationInchesPerHour / 12 * InfiltAreaAt75PercentDepth1SqFt / 3600;
        default:
          return _catchment.ImportedMediumInfiltrationInchesPerHour / 12 * BottomAreaSqFt / 3600;
      }
    }

    /// <summary>
    /// The infiltration rate through the native soil in cfs
    /// </summary>
    /// <returns></returns>
    public double NativeSoilInfiltrationCapacityCfs()
    {
      if (_isLined) return 0;

      if (_hasRockStorage && _hasCustomRockStorageBottomArea)
        return (_catchment.DesignInfiltrationNativeInchesPerHour / 12) * RockStorageBottomAreaSqFt / 3600;
      else
        return (_catchment.DesignInfiltrationNativeInchesPerHour / 12) * BottomAreaSqFt / 3600;
    }

    private double CalculateVolume(FacilityType type, FacilityShape shape, double depth, double surfaceAreaAtDepth)
    {
      double volume = 0;
      switch (type)
      {
        case FacilityType.Basin:
          if (shape == FacilityShape.Rectangle)
          {
            volume = //LWD + (L+W)XD^2 + (4/3)X^2D^3
                (_bottomAreaSqFt * depth / 12) //L*W*D
              +
                (_bottomWidthFt + _bottomAreaSqFt / _bottomWidthFt)
                * _sideSlopeRatio
                * Math.Pow(depth / 12, 2) //(L+W)*XD^2
              +
                (4.0 / 3) * Math.Pow(_sideSlopeRatio, 2) * Math.Pow(depth / 12, 3) //(4/3)X^2D^3
            ;
            break;
          }
          else if (shape == FacilityShape.Amoeba)
          {
            volume = ((depth / 12) * _sideSlopeRatio * surfaceAreaAtDepth) / 2 + (_bottomAreaSqFt * (depth / 12));
          }
          else if (shape == FacilityShape.UserDefined)
          {
            volume = (depth / 12) * (_bottomAreaSqFt + surfaceAreaAtDepth) / 2;
          }
          break;
        case FacilityType.PlanterFlat:
          volume = depth / 12 * _bottomAreaSqFt;
          break;
        case FacilityType.PlanterSloped: // overridden in SlopedFacility class
          if (SANITIZEINPUT)
            throw new NotImplementedException();
          else
            return 0;
        case FacilityType.Swale: // overridden in SlopedFacility class
          if (SANITIZEINPUT)
            throw new NotImplementedException();
          else
            return 0;
        default:
          if (SANITIZEINPUT)
            throw new ArgumentException("Volume calculation not defined");
          else
            return 0;          
      }
      return volume;
    }

    /// <summary>
    /// The pore space of the growing medium
    /// </summary>
    public double GrowingMediumPorespace
    {
      get { return DefaultGrowingMediumPorespace; }
    }

    /// <summary>
    /// The storage volume within the rock gallery, in cubic feet
    /// </summary>
    public virtual double RockStorageCapacityCuFt
    {
      get
      {
        if (_hasRockStorage)
        {
          double rockStorageBottomArea = _hasCustomRockStorageBottomArea ?
          _rockStorageBottomAreaSqFt : _bottomAreaSqFt;

          double rockStorageDepth = _hasRockInfluencedSurfaceStorage ?
          _rockStorageDepthIn : Math.Min(_storageDepth3In, _rockStorageDepthIn);

          return rockStorageBottomArea * rockStorageDepth / 12 * _rockVoidRatio;
        }
        else
        {
          return 0;
        }

      }
    }

    protected virtual void ConfigureFacility()
    {
      if (_configuration != FacilityConfiguration.A)
        _hasRockStorage = true;
      else
        _hasRockStorage = false;

      if (_configuration == FacilityConfiguration.E)
        _hasSecondaryOverflow = true;
      else
        _hasSecondaryOverflow = false;

      if (_configuration == FacilityConfiguration.D)
        _isLined = true;
      else
        _isLined = false;

      if (_type == FacilityType.Basin)
      {
        _hasCustomRockStorageBottomArea = true;
        _specifyFreeboard = true;
        _specifySideSlope = true;
        _allowShapeSelection = true;
      }      
      else
      {
        _hasCustomRockStorageBottomArea = false;
        _specifyFreeboard = false;
        _specifySideSlope = false;
        _allowShapeSelection = false;
      }

      if (_configuration == FacilityConfiguration.A ||
        _configuration == FacilityConfiguration.B ||
        _configuration == FacilityConfiguration.E)
      {
        _hasRockInfluencedSurfaceStorage = true;
      }
      else
      {
        _hasRockInfluencedSurfaceStorage = false;
      }

    }

    /// <summary>
    /// Indicates whether a facility has passed validation
    /// </summary>
    public bool IsValid
    {
      get { return _validConfiguration; }
    }

    /// <summary>
    /// Performs checks to determine whether a Facility has required parameters assigned, and that
    /// all parameters are valid.
    /// </summary>
    /// <param name="facility">The Facility to validate</param>
    /// <param name="category">The Hierarchy Category the facility will be validated against</param>
    /// <param name="message">A message indicating the first error found in an invalid facility,
    /// or "Valid Configuration" if the facility is valid.</param>
    /// <returns>True if the facility parameters are valid, otherwise false</returns>
    public static bool Validate(Facility facility, int category, out string message)
    {
      facility._validConfiguration = true;

      switch (category)
      {
        case 1:
          if (facility._configuration != FacilityConfiguration.A
            && facility._configuration != FacilityConfiguration.B)
          {
            message = "Only Configuration A or B may be used for Category 1";
            facility._validConfiguration = false;
            return false;
          }
          break;
        case 2:
          break;
        case 3:
          if (facility._configuration == FacilityConfiguration.E
            || facility._configuration == FacilityConfiguration.F)
          {
            message = "Configuration E and F may not be used for Category 3";
            facility._validConfiguration = false;
            return false;
          }
          break;
        case 4:
          if (facility._configuration == FacilityConfiguration.E
            || facility._configuration == FacilityConfiguration.F)
          {
            message = "Configuration E and F may not be used for Category 4";
            facility._validConfiguration = false;
            return false;
          }
          break;
        default:
          message = "Unknown configuration, unable to validate facility";
          facility._validConfiguration = false;
          return false;
      }

      message = "Valid configuration";

      return facility._validConfiguration;
    }

  }

  /// <summary>
  /// Enumerator list valid facilty types
  /// </summary>
  public enum FacilityType { Swale, Basin, PlanterFlat, PlanterSloped };

  /// <summary>
  /// Enumerator listing valid facility shapes
  /// </summary>
  public enum FacilityShape { Rectangle, Amoeba, Sloped, UserDefined };


  /// <summary>
  /// Enumerator listing various facility configurations
  /// </summary>
  public enum FacilityConfiguration { A = 'A', B = 'B', C = 'C', D = 'D', E = 'E', F = 'F' };

  /// <summary>
  /// Enumerator listing available stormwater management categories
  /// </summary>
  public enum HierarchyCategory { Category1 = 1, Category2 = 2, Category3 = 3, Category4 = 4 };

  /// <summary>
  /// Enumerator listing various discharge points
  /// A = Direct discharge to a river of Multnomah County Drainage District facility
  /// B = Discharge to an overland storm drainage system, such as a stream, drainageway or ditch either directly or through a storm pipe system
  /// C = Discharge to a storm sewer or drainage system that does not meet above criteria
  /// </summary>
  public enum DischargePoint { A = 'A', B = 'B', C = 'C' };

}
